package com.guanyu.u2cache.redis;

import com.guanyu.u2cache.elect.ElectMaster;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.PostConstruct;
import java.security.SecureRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author zhouyunjian
 * @date 2023/11/28 10:29
 **/
@Slf4j
public class ElectMasterImpl extends ElectMaster implements Runnable {
    /**
     * redis机器锁名称:多台机器抢夺同一个锁
     ***/
    @Value("${redisson.lockKey:redisLock}")
    private String lockKey;
    /**
     * redis锁延时:锁到期后默认延时5s
     ***/
    @Value("${redisson.delayTime:10}")
    private int delayTime;
    /**
     * redis锁接管:锁挂掉后被其他服务快速接管:默认在5s接管
     ***/
    @Value("${redisson.deliveTime:5}")
    private int deliveTime;
    /**
     * redis锁加载:redis服务锁是否加载
     ***/
    @Value("${redisson.useLock:true}")
    private volatile boolean use;
    /**
     * redisson客户端
     ***/
    @Autowired(required = false)
    private RedissonClient redissonCient;
    /**
     * 本地服务
     ***/
    private Thread thread;
    /**
     * 第一次抢到锁释放缓存信息，防止服务重启持有锁后保留未处理旧数据：0-锁断开过 1-第一次拿到锁定-需要清空缓存 >1多次拿到锁,无需清空
     ***/
    private AtomicInteger first = new AtomicInteger();
    /**
     * redisson锁
     ***/
    RLock lock = null;

    @PostConstruct
    public void init() {
        log.debug("[redislock][init]服务锁初始化加载开始");
        if (!use) {
            log.debug("[redislock][init]服务锁无需初始化");
            return;
        }
        thread = new Thread(this, ElectMasterImpl.class + "-" + new SecureRandom().nextInt(100));
        thread.setDaemon(true);
        thread.start();
        log.debug("[redislock][init]服务锁初始化加载完成");
    }

    @Override
    public void run() {
        while (use) {
            try {
                electMaster();
            } catch (Exception e) {
                log.error("[error][redislock]redis finally 解锁异常，等待[{}]s后再处理", delayTime);
                try {
                    TimeUnit.SECONDS.sleep(delayTime);
                } catch (InterruptedException interruptedException) {
                    log.error("[error][redislock]redis等待被打断异常[{}]", e.getMessage(), e);
                }
            }
        }
    }

    /**
     * 功能描述: redis锁管理。系统启动时多台服务去抢一把锁，并不断延迟此锁的过期时间;</br>
     * 其他未抢到锁的服务在不断检测此锁是否存在；如果不存在则抢锁，如果存在在继续等待。</br>
     * 使用场景: 机器持有锁 </br>
     * 处理流程:  </br>
     * 1、服务启动加锁，并设置锁超时时间(watch dog机制会自动管理超市处理);</br>
     * 2、服务轮询，如果没有抢到锁，则每隔5s等待后重新获取一次,直到接管此锁 ;</br>
     * <p>
     * 测试场景：
     * 1、多机抢锁测试
     * 2、单机器挂掉，其他机器接管测试
     * 3、网络闪断情况下，到恢复过程中，重新抢锁测试
     * 4、机器正常关闭，是否快速释放锁，他机快速接管锁
     *
     * @author zhouyunjian
     * @date 2023/11/25
     */
    @Override
    public void electMaster() {
        // 获取锁状态是否以及锁定
        lock = redissonCient.getLock(lockKey);
        log.debug("[redislock][start]redis抢锁开始，判断本机是否持有锁[{}]当前key[{}]是否被锁定[{}]", isMaster.get(), lockKey, lock.isLocked());
        first.set(0);
        try {
            // 有锁等待5s，watch dog 默认延时30s，如果到时间程序未结束，则自动续命
            isMaster.set(lock.tryLock(delayTime, TimeUnit.SECONDS));
            // lock.tryLock(30, delayTime, TimeUnit.SECONDS); 此方式未最多等待30s,到时间后不会自动续命
            // 如果获取到锁，则服务进入等待
            while (isMaster.get() && use) {
                //log.debug("[redislock]本机获取到服务锁[{}]，进入等待,一直维持持有锁", isMaster.get());
                //循环等待5s
                TimeUnit.SECONDS.sleep(delayTime);
                //log.debug("[redislock]本机等待结束，继续往下走，此时持有锁状态[{}]", isMaster.get());
                //如果use为false,跳出循环，此处当服务关闭时处理
                //如果网络闪断，超过30s，会导致加锁断开，需要重新抢一次
                lock = redissonCient.getLock(lockKey);
                if (!lock.isLocked()) {
                    log.debug("[redislock]本机加锁[{}]连接断开", isMaster.get());
                    electMaster();
                    first.set(0);
                }
            }
            while (!isMaster.get() && use) {
                log.debug("[redislock]本机未获取到服务锁[{}],[{}]s后重新尝试获取一次", isMaster.get(), deliveTime);
                // 等待1s后重新加锁
                TimeUnit.SECONDS.sleep(deliveTime);
                electMaster();
                first.set(0);
            }
        } catch (InterruptedException ie) {
            log.error("[error][redislock]redis锁等待打断异常[{}][{}]", ie.getMessage(), ie);
        } catch (RedisException ie) {
            log.error("[error][redislock]redis连接、关闭、加锁异常[{}][{}]", ie.getMessage(), ie);
        } catch (Exception e) {
            log.error("[error][redislock]redis服务处理总异常[{}][{}]", e.getMessage(), e);
        } finally {
            //如果机器网络断开，此服务将状态位置为false,forceUnlock强制解锁(如果为unlock则需要等待看门狗30s后才可解锁)
            if (isMaster.get()) {
                isMaster.set(false);
                lock.forceUnlock();
            }
            first.set(0);
            log.debug("[redislock]报异常，锁key[{}]解除锁定，锁状态位重置[{}]", lockKey, isMaster);
        }
    }


}
